﻿using System.Text;
using System.Text.RegularExpressions;

namespace ContentHolder
{
    public sealed class CombinKeywordMatcher : IKeywordMatcher
    {
        private ushort[] _dict;
        private int[] _first;
        private IntDictionary[] _nextIndex;
        private int[] _end;
        private int[] _resultIndex;
        private int[] _keywordLengths;

        private readonly IDictionary<string, List<int>> _originalSourceMap;
        private readonly string[] _patterns;

        public CombinKeywordMatcher(IEnumerable<string> keywords)
        {
            this._originalSourceMap = new Dictionary<string, List<int>>();
            var patterns = new List<string>();
            var source = new List<string>();
            foreach (string keyword in keywords)
            {
                patterns.Add(keyword.Replace("*", ".{0,10}"));
                var itemKeys = keyword.Split('*', StringSplitOptions.RemoveEmptyEntries);
                foreach (var itemKey in itemKeys)
                {
                    if (!this._originalSourceMap.TryGetValue(itemKey, out var list))
                    {
                        list = new List<int>();
                    }
                    list.Add(patterns.Count - 1);
                    this._originalSourceMap[itemKey] = list;
                }
                source.AddRange(itemKeys.Distinct());
            }
            this._patterns = [.. patterns];

            var newSource = source.Distinct();

            _keywordLengths = new int[newSource.Count()];
            int index = 0;
            foreach (var item in newSource)
            {
                _keywordLengths[index++] = item.Length;
            }

            this.SetKeywords(newSource);
        }

        private void SetKeywords(IEnumerable<string> _keywords)
        {
            var root = new TrieNode();
            var allNodeLayers = new Dictionary<int, List<TrieNode>>();
            int kindex = 0;
            foreach (string p in _keywords)
            {
                var nd = root;
                for (int j = 0; j < p.Length; j++)
                {
                    nd = nd.Add((char)p[j]);
                    if (nd.Layer == 0)
                    {
                        nd.Layer = j + 1;
                        List<TrieNode> trieNodes;
                        if (allNodeLayers.TryGetValue(nd.Layer, out trieNodes) == false)
                        {
                            trieNodes = new List<TrieNode>();
                            allNodeLayers[nd.Layer] = trieNodes;
                        }
                        trieNodes.Add(nd);
                    }
                }
                nd.SetResults(kindex++);
            }

            var allNode = new List<TrieNode>();
            allNode.Add(root);
            foreach (var trieNodes in allNodeLayers)
            {
                foreach (var nd in trieNodes.Value)
                {
                    allNode.Add(nd);
                }
            }
            allNodeLayers = null;

            for (int i = 1; i < allNode.Count; i++)
            {
                var nd = allNode[i];
                nd.Index = i;
                TrieNode r = nd.Parent.Failure;
                char c = nd.Char;
                while (r != null && (r.m_values == null || !r.m_values.ContainsKey(c))) r = r.Failure;
                if (r == null)
                    nd.Failure = root;
                else
                {
                    nd.Failure = r.m_values[c];
                    if (nd.Failure.Results != null)
                    {
                        foreach (var result in nd.Failure.Results)
                            nd.SetResults(result);
                    }
                }
            }
            root.Failure = root;

            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 1; i < allNode.Count; i++)
            {
                stringBuilder.Append(allNode[i].Char);
            }
            var length = CreateDict(stringBuilder.ToString());
            stringBuilder = null;

            var first = new int[Char.MaxValue + 1];
            if (allNode[0].m_values != null)
            {
                foreach (var item in allNode[0].m_values)
                {
                    var key = (char)_dict[item.Key];
                    first[key] = item.Value.Index;
                }
            }
            _first = first;

            var resultIndex2 = new List<int>();
            var isEndStart = new List<bool>();
            var _nextIndex2 = new IntDictionary[allNode.Count];

            for (int i = allNode.Count - 1; i >= 0; i--)
            {
                var dict = new Dictionary<ushort, int>();
                var result = new List<int>();
                var oldNode = allNode[i];

                if (oldNode.m_values != null)
                {
                    foreach (var item in oldNode.m_values)
                    {
                        var key = (char)_dict[item.Key];
                        var index = item.Value.Index;
                        dict[key] = index;
                    }
                }
                if (oldNode.Results != null)
                {
                    foreach (var item in oldNode.Results)
                    {
                        if (result.Contains(item) == false)
                        {
                            result.Add(item);
                        }
                    }
                }

                oldNode = oldNode.Failure;
                while (oldNode != root)
                {
                    if (oldNode.m_values != null)
                    {
                        foreach (var item in oldNode.m_values)
                        {
                            var key = (char)_dict[item.Key];
                            var index = item.Value.Index;
                            if (dict.ContainsKey(key) == false)
                            {
                                dict[key] = index;
                            }
                        }
                    }
                    if (oldNode.Results != null)
                    {
                        foreach (var item in oldNode.Results)
                        {
                            if (result.Contains(item) == false)
                            {
                                result.Add(item);
                            }
                        }
                    }
                    oldNode = oldNode.Failure;
                }
                _nextIndex2[i] = new IntDictionary(dict);

                if (result.Count > 0)
                {
                    for (int j = result.Count - 1; j >= 0; j--)
                    {
                        resultIndex2.Add(result[j]);
                        isEndStart.Add(false);
                    }
                    isEndStart[isEndStart.Count - 1] = true;
                }
                else
                {
                    resultIndex2.Add(-1);
                    isEndStart.Add(true);
                }
                dict = null;
                result = null;
                allNode[i].Dispose();
                allNode.RemoveAt(i);
            }
            allNode.Clear();
            allNode = null;
            root = null;
            _nextIndex = _nextIndex2;

            var resultIndex = new List<int>();
            var end = new List<int>() { };
            for (int i = isEndStart.Count - 1; i >= 0; i--)
            {
                if (isEndStart[i])
                {
                    end.Add(resultIndex.Count);
                }
                if (resultIndex2[i] > -1)
                {
                    resultIndex.Add(resultIndex2[i]);
                }
            }
            end.Add(resultIndex.Count);
            _resultIndex = resultIndex.ToArray();
            _end = end.ToArray();
        }

        private int CreateDict(string keywords)
        {
            var dictionary = new Dictionary<char, Int32>();
            foreach (var item in keywords)
            {
                if (dictionary.ContainsKey(item))
                {
                    dictionary[item] += 1;
                }
                else
                {
                    dictionary[item] = 1;
                }
            }
            var list = dictionary.OrderByDescending(q => q.Value).Select(q => q.Key).ToList();
            var list2 = new List<char>();
            var sh = false;
            foreach (var item in list)
            {
                if (sh)
                {
                    list2.Add(item);
                }
                else
                {
                    list2.Insert(0, item);
                }
                sh = !sh;
            }
            _dict = new ushort[char.MaxValue + 1];
            for (Int32 i = 0; i < list2.Count; i++)
            {
                _dict[list2[i]] = (ushort)(i + 1);
            }
            return dictionary.Count;
        }

        public IEnumerable<string> Matching(string source)
        {
            var matchedList = new List<int>();
            var matchedKeywords = new List<string>();
            foreach (var result in this.FindAll(source))
            {
                if (!this._originalSourceMap.TryGetValue(result.Keyword, out var list))
                {
                    continue;
                }
                foreach (var item in list.Where(i => !matchedList.Contains(i)))
                {
                    try
                    {
                        if (Regex.IsMatch(source, this._patterns[item]))
                        {
                            matchedKeywords.Add(this._patterns[item]);
                        }
                    }
                    catch (Exception ex)
                    {
                    }

                    matchedList.Add(item);
                }
            }

            return matchedKeywords;
        }

        List<WordsSearchResult> FindAll(string text)
        {
            var result = new List<WordsSearchResult>();
            var p = 0;
            var txt = text.AsSpan();
            for (int i = 0; i < txt.Length; i++)
            {
                var t = _dict[txt[i]];
                if (t == 0)
                {
                    p = 0;
                    continue;
                }
                if (p == 0 || _nextIndex[p].TryGetValue(t, out int next) == false)
                {
                    next = _first[t];
                }
                if (next != 0)
                {
                    for (int j = _end[next]; j < _end[next + 1]; j++)
                    {
                        var index = _resultIndex[j];
                        var len = _keywordLengths[index];
                        var st = i + 1 - len;
                        var r = new WordsSearchResult(ref text, st, i, index);
                        result.Add(r);
                    }
                }
                p = next;
            }
            return result;
        }

        List<WordsSearchResult> FindAll2(string text)
        {
            var result = new List<WordsSearchResult>();
            var p = 0;
            for (int i = 0; i < text.Length; i++)
            {
                var t = _dict[text[i]];
                if (t == 0)
                {
                    p = 0;
                    continue;
                }
                int next;
                if (p == 0 || _nextIndex[p].TryGetValue(t, out next) == false)
                {
                    next = _first[t];
                }
                if (next != 0)
                {
                    for (int j = _end[next]; j < _end[next + 1]; j++)
                    {
                        var index = _resultIndex[j];
                        var len = _keywordLengths[index];
                        var st = i + 1 - len;
                        var r = new WordsSearchResult(ref text, st, i, index);
                        result.Add(r);
                    }
                }
                p = next;
            }
            return result;
        }

        sealed class WordsSearchResult
        {
            private readonly string _text;
            private string? _keyword;
            private string? _matchKeyword;

            /// <summary>
            /// 开始位置
            /// </summary>
            public int Start { get; private set; }

            /// <summary>
            /// 结束位置
            /// </summary>
            public int End { get; private set; }

            /// <summary>
            /// 关键字
            /// </summary>
            public string Keyword
            {
                get
                {
                    if (_keyword == default)
                    {
                        _keyword = _text[Start..(End + 1)];
                    }
                    return _keyword;
                }
            }

            /// <summary>
            /// 索引
            /// </summary>
            public int Index { get; private set; }

            /// <summary>
            /// 匹配关键字
            /// </summary>
            public string MatchKeyword
            {
                get
                {
                    if (_matchKeyword == default)
                    {
                        _matchKeyword = _keyword ?? _text[Start..(End + 1)];
                    }
                    return _matchKeyword;
                }
            }

            public WordsSearchResult(string keyword, int start, int end, int index)
            {
                _keyword = keyword;
                End = end;
                Start = start;
                Index = index;
                _matchKeyword = keyword;
            }

            public WordsSearchResult(ref string text, int start, int end, int index)
            {
                _text = text;
                End = end;
                Start = start;
                Index = index;
            }

            public WordsSearchResult(string keyword, int start, int end, int index, string matchKeyword)
            {
                _keyword = keyword;
                End = end;
                Start = start;
                Index = index;
                _matchKeyword = matchKeyword;
            }

            public override string ToString()
            {
                if (MatchKeyword != Keyword)
                {
                    return Start.ToString() + "|" + Keyword + "|" + MatchKeyword;
                }

                return Start.ToString() + "|" + Keyword;
            }
        }
    }
}
